﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data.Activation;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.Messenger;
using StyleMVVM.View.Regions.Messages;

namespace StyleMVVM.View.Regions
{
	public sealed class RegionManager : IRegionManager, IDependencyInjectionContainerAware, IActivationAware
	{
		private static readonly string supplemental = typeof(RegionManager).FullName;
		private readonly Dictionary<Type, IRegionAdapter> adapters = new Dictionary<Type, IRegionAdapter>();
		private IEnumerable<IViewBinder> viewBinders;
		private IDispatchedMessenger messenger;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(RegionManager)).
			          As(typeof(IRegionManager)).
			          ImportMethod("ImportMethod", true).
			          ActivationMethod("Activate").
			          CoreExport(CoreExportStartOrder.Normal).
			          AndSharedPermenantly();
		}

		public void ImportMethod(IDispatchedMessenger messenger, IEnumerable<IRegionAdapter> regionAdapters, IEnumerable<IViewBinder> viewBinders)
		{
			this.messenger = messenger;
			this.viewBinders = viewBinders;

			foreach (object o in regionAdapters)
			{
				IRegionAdapter adapter = o as IRegionAdapter;

				if (adapter != null && !adapters.ContainsKey(adapter.AddapterType))
				{
					adapters.Add(adapter.AddapterType, adapter);
				}
			}
		}

		public void Activate()
		{
			Regions = new RegionMap();

			messenger.Register(new Action<AddToRegion>(AddToRegionHandler));
			messenger.Register(new Action<RemoveFromRegion>(RemoveFromRegion));
			messenger.Register(new Action<NavigateRegion>(NavigateRegion));
		}

		public IDependencyInjectionContainer Container { get; set; }

		public IRegion CreateRegion(object regionObject, string name, string context)
		{
			return CreateRegion(regionObject, name, context, null);
		}

		public IRegion CreateRegion(object regionObject, string name, string context, IRegionAdapter adapter)
		{
			IRegion returnValue = null;

			if (adapter == null)
			{
				adapter = FindRegionAdapter(regionObject.GetType());
			}

			if (adapter != null)
			{
				returnValue = new BaseRegion(name, context, adapter, viewBinders);

				returnValue.RegionManager = this;
				returnValue.Hook(regionObject);
			}
			else
			{
				throw new Exception("Could not create region for Type: " + regionObject.GetType());
			}

			return returnValue;
		}

		public IRegion FindRegion(string name)
		{
			return FindRegion(name, null);
		}

		public IRegion FindRegion(string name, string context)
		{
			IRegion returnValue = null;
			string fullName = name;

			if (!string.IsNullOrEmpty(context))
			{
				fullName = string.Concat(name, '|', context);
			}

			Regions.TryGetValue(fullName, out returnValue);

			return returnValue;
		}

		public IRegionMap Regions { get; private set; }

		#region Message Handlers

		public async void AddToRegionHandler(AddToRegion message)
		{
			try
			{
				if (!string.IsNullOrEmpty(message.RegionName))
				{
					IRegion region = FindRegion(message.RegionName, message.ContextName);

					if (region != null)
					{
						if (!string.IsNullOrEmpty(message.ExportName))
						{
							await region.Add(message.ExportName, message.NavigationParameter);
						}
						else if (message.Export != null)
						{
							await region.Add(message.Export, message.NavigationParameter);
						}
						else
						{
							Logger.Error("Received AddToRegion message with an empty Export & ExportName property.", supplemental);
						}
					}
					else
					{
						Logger.Error("Could not find region by the name of: " + message.ExportName, supplemental);
					}
				}
				else
				{
					Logger.Error("Received AddToRegion message with a blank RegionName property", supplemental);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while handing add region", supplemental, exp);
			}
		}

		public async void NavigateRegion(NavigateRegion message)
		{
			try
			{
				if (!string.IsNullOrEmpty(message.RegionName))
				{
					IRegion region = FindRegion(message.RegionName, message.ContextName);

					if (region != null)
					{
						if (!string.IsNullOrEmpty(message.ExportName))
						{
							await region.Navigate(message.ExportName, message.NavigationParameter);
						}
						else
						{
							Logger.Error("Received NavigateRegion message with empty ExportName property", supplemental);
						}
					}
					else
					{
						Logger.Error("Could not find region by the name: " + message.RegionName, supplemental);
					}
				}
				else
				{
					Logger.Error("Received NavigateRegion message with blank region name", supplemental);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while handing navigate region", supplemental, exp);
			}
		}

		public async void RemoveFromRegion(RemoveFromRegion message)
		{
			try
			{
				if (!string.IsNullOrEmpty(message.RegionName))
				{
					IRegion region = FindRegion(message.RegionName, message.ContextName);

					if (region != null)
					{
						if (!string.IsNullOrEmpty(message.ExportName))
						{
							await region.Remove(message.ExportName);
						}
						else if (message.Export != null)
						{
							await region.Remove(message.Export);
						}
						else
						{
							Logger.Error("Received RemoveFromRegion message with a blank ExportName and Export property", supplemental);
						}
					}
					else
					{
						Logger.Error("Could not find region by the name of: " + message.RegionName);
					}
				}
				else
				{
					Logger.Error("Received RemoveFromRegion message with an empty RegionName property", supplemental);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while handing remove from region", supplemental, exp);
			}
		}

		#endregion

		private IRegionAdapter FindRegionAdapter(Type objectType)
		{
			IRegionAdapter returnValue = null;

			if (objectType != null)
			{
				adapters.TryGetValue(objectType, out returnValue);

				if (returnValue == null)
				{
					return FindRegionAdapter(objectType.GetTypeInfo().BaseType);
				}
			}

			return returnValue;
		}
	}
}